"""
ipython的颜色不是由qss决定的，而是由自身决定的。
qss在这个文件中定义。

Created on 2020/8/24
@author: Irony
@email: 892768447@qq.com
@file: console.py
@description: Console Widget
"""
import os
from PyQt5.QtCore import QObject, pyqtSignal, QThread, QWaitCondition, QMutex
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QMessageBox, QMenu
from qtconsole.manager import QtKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtconsole import styles
from qtconsole.styles import default_light_syntax_style, default_light_style_sheet

default_dark_style_template = styles.default_template + """\
    .in-prompt { color: #ff00ff; }
    .out-prompt { color: #ff0000; }
"""
default_dark_style_sheet = default_dark_style_template % dict(
    bgcolor='#19232d', fgcolor='white', select="#ccc")
default_dark_syntax_style = 'default'


class ConsoleInitThread(QObject):
    initialized = pyqtSignal(object, object)

    def __init__(self, *args, **kwargs):
        super(ConsoleInitThread, self).__init__(*args, **kwargs)
        self.mutex = QMutex()
        self.wait_condition = QWaitCondition()

    def run(self):
        self.mutex.lock()
        kernel_manager = QtKernelManager(kernel_name='python3')
        kernel_manager.start_kernel()

        kernel_client = kernel_manager.client()
        kernel_client.start_channels()

        # notify to update ui
        self.initialized.emit(kernel_manager, kernel_client)

        # wait for exit
        self.wait_condition.wait(self.mutex)
        self.mutex.unlock()

        # stop channels and kernel
        kernel_client.stop_channels()
        kernel_manager.shutdown_kernel(now=True)  # add now=True; Fix exit error;  200924 liugang

    def stop(self):
        self.wait_condition.wakeAll()


class PMGIpythonConsole(RichJupyterWidget):

    def __init__(self, *args, **kwargs):
        super(PMGIpythonConsole, self).__init__(*args, **kwargs)
        self.is_first_execution = True
        self.confirm_restart = False

        self.commands_pool = []

    def change_ui_theme(self, style: str):
        """
        改变界面主题颜色
        :param style:
        :return:
        """
        if style == 'Fusion':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

        elif style == 'Qdarkstyle':
            self.style_sheet = default_dark_style_sheet
            self.syntax_style = default_dark_syntax_style

        elif style.lower() == 'windowsvista':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

        elif style.lower() == 'windows':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

    def _handle_kernel_died(self, since_last_heartbit):
        self.is_first_execution = True
        self.restart_kernel(None, True)
        self.initialize_ipython_builtins()
        self.execute_command('')
        return True

    def _handle_execute_input(self, msg):
        super()._handle_execute_result(msg)

    def setup_ui(self):
        self.kernel_manager = None
        self.kernel_client = None
        # initialize by thread
        self.init_thread = QThread(self)
        self.console_object = ConsoleInitThread()
        self.console_object.moveToThread(self.init_thread)
        self.console_object.initialized.connect(self.slot_initialized)
        self.init_thread.finished.connect(self.console_object.deleteLater)
        self.init_thread.finished.connect(self.init_thread.deleteLater)
        self.init_thread.started.connect(self.console_object.run)
        self.init_thread.start()
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)

        _ = lambda s: s
        self.context_menu = QMenu()
        restart_action = self.context_menu.addAction(_('Restart'))
        restart_action.triggered.connect(self._restart_kernel)

    def _custom_context_menu_requested(self, pos):
        self.context_menu.exec_(self.mapToGlobal(pos))

    def _restart_kernel(self, arg1):
        self.is_first_execution = True
        self.restart_kernel(None, True)
        self.initialize_ipython_builtins()
        self.execute_command('')
        return True

    def slot_initialized(self, kernel_manager, kernel_client):
        """
        Args:
            kernel_manager: `qtconsole.manager.QtKernelManager`
            kernel_client: `qtconsole.manager.QtKernelManager.client`

        Returns:
        """
        self.kernel_manager = kernel_manager
        self.kernel_client = kernel_client
        self.initialize_ipython_builtins()

    def initialize_ipython_builtins(self):
        return

    def _update_list(self):
        try:
            super(PMGIpythonConsole, self)._update_list()
        except BaseException:
            import traceback
            traceback.print_exc()

    def _banner_default(self):
        """
        自定义控制台开始的文字
        Returns:
        """
        return 'Welcome To PMGWidgets Ipython Console!\n'

    def closeEvent(self, event):
        if self.init_thread.isRunning():
            self.console_object.stop()
            self.init_thread.quit()
            self.init_thread.wait(500)
        super(PMGIpythonConsole, self).closeEvent(event)

    def execute_file(self, file: str, hidden: bool = False):
        if not os.path.exists(file) or not file.endswith('.py'):
            raise FileNotFoundError(f'{file} not found or invalid')
        base = os.path.basename(file)
        cmd = os.path.splitext(base)[0]
        with open(file, 'r', encoding='utf-8') as f:
            source = f.read()

        self.execute_command(source, hidden=hidden, hint_text=cmd)

    def execute_command(self, source, hidden: bool = False,
                        hint_text: str = ''):
        """

        :param source:
        :param hidden:
        :param hint_text: 运行代码前显示的提示
        :return:
        """
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)
        # 运行文件时,显示文件名,无换行符,执行选中内容时,包含换行符
        # 检测换行符,在ipy console中显示执行脚本内容
        hint_row_list = hint_text.split("\n")
        for hint in hint_row_list:
            if hint != "":
                cursor.insertText('%s\n' % hint)
                self._insert_continuation_prompt(cursor)
        else:
            # 删除多余的continuation_prompt
            self.undo()

        self._finalize_input_request()  # display input string buffer in console.
        cursor.movePosition(QTextCursor.End)
        if self.kernel_client is None:
            self.commands_pool.append((source, hidden, hint_text))

        else:
            self._execute(source, hidden)

    def _handle_stream(self, msg):
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)
        super()._handle_stream(msg)

    def append_stream(self, text):
        """重写的方法。原本before_prompt属性是False。"""
        self._append_plain_text(text, before_prompt=False)

    def _execute(self, source: str, hidden: bool = False):
        if not self.is_source_code_legal(source):
            QMessageBox.warning(self, '警告', '命令\n\"%s\"\n为暂不支持的命令。' % source)
            source = ''
        super()._execute(source, hidden)

    def is_source_code_legal(self, source_code: str) -> bool:
        """
        判断注入到shell的命令是否合法，不合法的话，就避免执行这个函数。
        :param source_code:
        :return:
        """
        return True


if __name__ == '__main__':
    import sys
    import cgitb
    from PyQt5.QtWidgets import QApplication

    cgitb.enable(format='text')

    app = QApplication(sys.argv)
    w = PMGIpythonConsole()
    w.show()
    w.setup_ui()
    sys.exit(app.exec_())
